package session

import (
	"github.com/garyburd/redigo/redis"
	"net/http"
	"strconv"
	"strings"
	"sync"
)

func init() {
	RegisterProvider("redis", redisProvider)
}

type RedisProvider struct {
	maxlifetime int
	path        string
	poolsize    int
	pools       *redis.Pool
}

type RedisSessionInstance struct {
	data        map[interface{}]interface{}
	sessionId   string
	lock        sync.RWMutex
	maxlifetime int
	pool        *redis.Pool
}

var redisProvider = &RedisProvider{}

/***** implements funcs of provider ****/

func (this *RedisProvider) SessionInit(path string, gctime int) error {
	this.maxlifetime = gctime
	pathArr := strings.Split(path, ",")
	if len(pathArr) > 0 {
		this.path = pathArr[0]
	}
	if len(pathArr) > 1 {
		ps, err := strconv.Atoi(pathArr[1])
		if err != nil || ps <= 0 {
			this.poolsize = 200
		} else {
			this.poolsize = ps
		}
	} else {
		this.poolsize = 200
	}
	this.pools = redis.NewPool(func() (redis.Conn, error) {
		c, err := redis.Dial("tcp", this.path)
		if err != nil {
			return nil, err
		}
		_, err = c.Do("SELECT", 0)
		if err != nil {
			c.Close()
			return nil, err
		}
		return c, nil
	}, this.poolsize)
	return this.pools.Get().Err()
}

func (this *RedisProvider) SessionExist(id string) bool {
	c := this.pools.Get()
	defer c.Close()

	if existed, err := redis.Int(c.Do("EXISTS", id)); err != nil || existed == 0 {
		return false
	} else {
		return true
	}
}

func (this *RedisProvider) SessionRead(id string) (SessionInstance, error) {
	c := this.pools.Get()
	defer c.Close()

	dataStr, err := redis.String(c.Do("GET", id))
	var data map[interface{}]interface{}
	if len(dataStr) == 0 {
		data = make(map[interface{}]interface{})
	} else {
		data, err = DecodeGob([]byte(dataStr))
		if err != nil {
			return nil, err
		}
	}

	result := &RedisSessionInstance{
		data:        data,
		sessionId:   id,
		pool:        this.pools,
		maxlifetime: this.maxlifetime,
	}
	return result, nil
}

func (this *RedisProvider) SessionDestory(id string) error {
	c := this.pools.Get()
	defer c.Close()

	c.Do("DEL", id)
	return nil
}

func (this *RedisProvider) SessionGC() {
	return
}

/******** inplements funcs of instance *********/

func (this *RedisSessionInstance) Set(key, value interface{}) error {
	this.lock.Lock()
	defer this.lock.Unlock()
	this.data[key] = value
	return nil
}

func (this *RedisSessionInstance) Get(key interface{}) interface{} {
	this.lock.RLock()
	defer this.lock.RUnlock()
	if v, ok := this.data[key]; ok {
		return v
	} else {
		return nil
	}
}

func (this *RedisSessionInstance) Delete(key interface{}) error {
	this.lock.Lock()
	defer this.lock.Unlock()
	delete(this.data, key)
	return nil
}

func (this *RedisSessionInstance) SessionId() string {
	return this.sessionId
}

func (this *RedisSessionInstance) SessionSave(w http.ResponseWriter) {
	c := this.pool.Get()
	defer c.Close()

	dataStr, err := EncodeGob(this.data)
	if err != nil {
		return
	}

	c.Do("SETEX", this.sessionId, this.maxlifetime, string(dataStr))
}

func (this *RedisSessionInstance) SessionClear() error {
	this.lock.Lock()
	defer this.lock.Unlock()
	this.data = make(map[interface{}]interface{})
	return nil
}
